#include"tcp_conn.h"
#include<fcntl.h>
#include<unistd.h>
#include<netinet/tcp.h>
#include<netinet/in.h>
#include"message.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include"tcp_server.h"

// 当前连接的处理客户端的一个业务（目前是回显操作）
void callback_busi(const char* data, int len, int id, void *args,tcp_conn *conn){
    // 让tcp_conn主动发一个消息给对方的client
    conn->send_message(data, len, id);
}



// 普通的读回调函数
void conn_rd_callback(event_loop* loop, int fd, void* args){
    tcp_conn* conn = (tcp_conn *)args;
    conn->do_read();
}

// 普通的写回调函数
void conn_wt_callback(event_loop* loop, int fd, void* args){
    tcp_conn* conn = (tcp_conn *)args;
    conn->do_write();
}

// 构造初始化
tcp_conn::tcp_conn(int connfd, event_loop *loop){
    _connfd = connfd;
    _loop = loop;

    // 1.将_connfd设置为非阻塞
    int flag = fcntl(_connfd, F_SETFL, 0);  //将_connfd的全部状态清空
    fcntl(_connfd, F_SETFL, O_NONBLOCK | flag);  //设置为非阻塞

    // 2.设置TCP_NODELAY状态；禁止读写缓存，降低小包延迟
    int op = 1;
    //需要头文件：netinet/in.h netinet/tcp.h
    setsockopt(_connfd, IPPROTO_TCP, TCP_NODELAY, &op, sizeof(op));
    
    // 3.创建连接后执行创建的Hook函数
    if(tcp_server::conn_start_cb != NULL){
        tcp_server::conn_start_cb(this, tcp_server::conn_start_cb_args);
    }

    // 4.将当前的tcp_conn的读事件加入到loop中进行监听
    _loop->add_io_event(_connfd, conn_rd_callback, EPOLLIN, this);

    // 5.将自己和fd添加到conns的集合中
    tcp_server::increase_conn(_connfd, this);
}


// 被动处理读事件的方法，由event_loop监听触发的
void tcp_conn::do_read(){
    // 1.从_connfd中读取数据
    int ret = ibuf.read_data(_connfd);
	if(ret == -1){
		fprintf(stderr,"ibuf read_data error\n");
		this->clean_conn();
		return ;
	}
	else if(ret == 0){
		//对方正常关闭，删除当前读事件
        printf("peer client closed!\n");
        this->clean_conn();
		return ;
	} 

    msg_head head;
    // 2.判断读取过来的数据是否满足8字节
    while(ibuf.length() >= MESSAGE_HEAD_LEN){
        // 2.1 先读头部信息，得到msgid和msglen
        memcpy(&head, ibuf.data(), MESSAGE_HEAD_LEN);
        if(head.msglen>MESSAGE_LENGTH_LIMIT || head.msglen<0){
            fprintf(stderr,"message format error too large or too small\n");
            this->clean_conn();
		    break;
        }

        // 2.2判断客户端是否读完
        if(ibuf.length()<MESSAGE_HEAD_LEN + head.msglen){
            // ibuf中缓存的数据并不是完整的数据包
            break;
        }
        
        // 2.3 当前ibuf缓存中的数据是完整的客户端数据
        ibuf.pop(MESSAGE_HEAD_LEN);
    
        // 处理数据
        // printf("recv data = %s\n", ibuf.data());

        // 针对不同的msgid调用不同的业务回调函数
        tcp_server::_router.call(head.msgid, ibuf.data(), head.msglen, this);

        //消息体处理完了,去掉msglen长度
        ibuf.pop(head.msglen);

    }

    ibuf.adjust();
    return ;
    
}

// 被动处理写事件的方法，由event_loop监听触发的
void tcp_conn::do_write(){

    //do_write表示obuf缓冲中已经有要写的数据了，就应该直接调用write2fd写出数据
    
    //只要obuf中有数据就写
    while(obuf.length()){
        int ret = obuf.write2fd(_connfd);
        if(ret == -1){
            fprintf(stderr,"tcp_conn obuf write2fd error\n");
            this->clean_conn();
            return ;
        }
        else if(ret == 0){
            // 当前不可写
            break;
        }
    }

    //数据已经全部写完，将_connfd的写事件取消掉
    if (obuf.length() == 0) {
        _loop->del_io_event(_connfd, EPOLLOUT);
    }

    return ;
}

// 主动发送消息的方法
int tcp_conn::send_message(const char* data, int msglen, int msgid){
    
    //如果现在已经数据都发送完了，那么是需要重新激活写事件
    //如果有数据，说明数据还没有完全写完，那么没必要再重复激活 
    bool active_epollout = false; 
    
    if(obuf.length() == 0) {
        active_epollout = true;
    }

    // 1.封装一个message包头
    msg_head head;
    head.msgid = msgid;
    head.msglen = msglen;
    // 将消息头写入obuf缓冲区
    int ret = obuf.send_data((const char *)&head, MESSAGE_HEAD_LEN);
    if(ret!=0){
        fprintf(stderr,"send head error\n");
        return -1;
    }
    // 2.写消息体
    ret = obuf.send_data(data,msglen);
    if(ret != 0){
        // 如果消息体写失败，则消息头需要弹出
        fprintf(stderr,"send data error\n");
        obuf.pop(MESSAGE_HEAD_LEN);
        return -1;
    }

    printf("tcp_conn::send_message:server send id:%d len:%d\n",msgid, msglen);

    // 3.将_connfd添加一个写EPOLLOUT事件，回调让obuf中的数据写回给对端客户端
    if (active_epollout == true) {
        _loop->add_io_event(_connfd, conn_wt_callback, EPOLLOUT, this);
    }

    return 0;
}

// 销毁当前链接
void tcp_conn::clean_conn(){
    // 1.删除连接前执行关闭的Hook函数
    if(tcp_server::conn_close_cb != NULL){
        tcp_server::conn_close_cb(this, tcp_server::conn_close_cb_args);
    }

    // 2.将tcp_server中将当前链接删掉
    tcp_server::decrease_conn(_connfd);

    // 3.连接的清理工作
    _loop->del_io_event(_connfd);

    // 4.buf清空
    ibuf.clear();
    obuf.clear();
    // 5.关闭套接字
    close(_connfd);
}


tcp_conn::~tcp_conn(){

}